package algebra.query;

import java.util.ArrayList;

import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import option.KBVocabulary;
import option.QueryKeywords;

//import kb.ABox;
import kb.DescriptionLogicsKB;
//import kb.TBox;
import reasoner.KBReason;
import utils.Util;
import utils.exception.CoreReasoningException;
import utils.exception.OrderingDescriptionException;
import entities.assertion.ConceptAssertion;
import entities.concept.AndConcept;
import entities.concept.Concept;
import entities.concept.DomainConcept;
import entities.concept.DomainConceptEQ;
import entities.concept.DomainConceptLT;
import entities.concept.NotConcept;
import entities.concept.OrConcept;
import entities.role.Role;

/**
 * As of Jan. 2013, a new and more complete ordering option is available in
 * OrderDep, i.e., order dependency.
 * 
 * @author j55wu
 * 
 */

@Deprecated
public class OrderingDescription {
	private static boolean ENABLE_OPTIMIZATIONS = true;
	private static Map<Concept, Concept> subsumptionCache = new HashMap<Concept, Concept>();

	private static int cacheHits = 0;
	private static int structuralSubsumptions = 0;
	// no KB need for ordering descrption trees
	// private DescriptionLogicsKB kb;
	private String type = "";

	private final String od;
	public OrderingDescription residualOd;
	public OrderingDescription residualOd2;
	public String arg;
	public Concept partition;

	public static OrderingDescription unOrdered;
	static {
		try {
			unOrdered = new OrderingDescription("(un)");
		} catch (OrderingDescriptionException e) {
			e.printStackTrace();
		}
	}

	/*
	 * Given an array of feature names, return an Od. N.B. No partitions for
	 * now.
	 */

	public static void main(String[] args) {
		String[] fs = { "g", "a", "h" };
		OrderingDescription od = null;
		try {
			od = getOdFeatureFromArray(new ArrayList<String>(Arrays.asList(fs)));
		} catch (OrderingDescriptionException e) {
			e.printStackTrace();
		}
		System.out.println(od);
	}

	public static OrderingDescription getOdFeatureFromArray(List<String> fs)
			throws OrderingDescriptionException {
		String os = "";
		if (fs == null || fs.size() < 1) {
			os = QueryKeywords.ORDERING_UN;
			return new OrderingDescription(os);
		}
		int i = 0;
		while (!fs.isEmpty()) {
			os += " (od-feature " + fs.remove(0);
			if (fs.size() < 1) {
				os += " un) ";
				break;
			} else {
				i++;
			}
		}
		for (; i > 0; i--) {
			os += " )";
		}
		// System.out.println(os);
		return new OrderingDescription(os);
	}

	public OrderingDescription(String od) throws OrderingDescriptionException {
		this.od = od;
		// this.kb = kb;
		if (!this.od.trim().equals("")) {
			parse(this.od);
		} else {
			// this Od will be created by tokens
			this.residualOd = null;
			this.residualOd2 = null;
			this.arg = null;
			this.partition = null;
		}
	}

	private void parse(String od) throws OrderingDescriptionException {
		od = od.trim();
		// System.out.println("PARSING: " + od);
		if (!od.startsWith(KBVocabulary.LEFTDEL)
				&& !od.startsWith(QueryKeywords.ORDERING_UN)) {
			throw new OrderingDescriptionException(
					"Malformed Ordering Description: " + od);
		}

		// top level Un: ordering by the individual names
		if (od.equalsIgnoreCase(QueryKeywords.ORDERING_UN)
				|| od.equalsIgnoreCase("(un)")) {
			setType(QueryKeywords.ORDERING_UN);
			return;
		}

		int space = od.indexOf(' ');
		String op = od.substring(1, space);
		int space2 = od.indexOf(' ', space + 1);
		if (op.compareToIgnoreCase(QueryKeywords.ORDERING_FEATURE) == 0) {
			this.arg = od.substring(space, space2).trim();
			this.residualOd = new OrderingDescription(od.substring(space2,
					od.length() - 1));
			setType(QueryKeywords.ORDERING_FEATURE);
		} else if (op.compareToIgnoreCase(QueryKeywords.ORDERING_PARTITION) == 0) {
			setType(QueryKeywords.ORDERING_PARTITION);
			if (od.charAt(space + 1) == '(') {
				space2 = space + findCloseParen(od.substring(space)) + 2;
			} else {
				space2 = od.indexOf(' ', space + 1) + 1;
			}
			// System.out.println("PARTITIONSTR: " + od.substring(space,
			// space2).trim());
			this.partition = Util.parseConcept(od.substring(space, space2)
					.trim(), null);
			space = space2;
			int idx = 0;
			if (od.charAt(space) == '(') {
				idx = space + findCloseParen(od.substring(space));
			} else {
				idx = od.indexOf(' ', space);
			}

			residualOd = new OrderingDescription(od.substring(space, idx));
			residualOd2 = new OrderingDescription(od.substring(idx + 1,
					od.length() - 1));
		} else {
			throw new OrderingDescriptionException(
					"Malformed Ordering Description: " + od);
		}
	}

	private int findCloseParen(String od) {
		od = od.trim();
		int open = 0;
		int i = 0;
		for (; i < od.length(); i++) {
			char c = od.charAt(i);
			if (c == ')')
				open--;
			else if (c == '(')
				open++;

			if (open == 0) {
				return i + 1;
			}
		}
		return -1;
	}

	// by default, un is assumed.
	public int compare(ConceptAssertion ca, ConceptAssertion ca2)
			throws OrderingDescriptionException, CoreReasoningException {
		if (getType().compareToIgnoreCase(QueryKeywords.ORDERING_FEATURE) == 0) {
			return compareFeature(ca, ca2);
		} else if (getType().compareToIgnoreCase(
				QueryKeywords.ORDERING_PARTITION) == 0) {
			return comparePartition(ca, ca2);
		} else {
			// compare on individual names
			return ca.getIndividual().getName()
					.compareTo(ca2.getIndividual().getName());
		}
	}

	private int compareFeature(ConceptAssertion ca, ConceptAssertion ca2)
			throws OrderingDescriptionException, CoreReasoningException {
		if (ENABLE_OPTIMIZATIONS) {
			// check for syntactic feature values first
			List<Concept> k1List = structuralFindK(ca, this.arg);
			if (!k1List.isEmpty()) {
				if (k1List.size() > 1) {
					throw new OrderingDescriptionException(
							"Concept not sufficiently descriptive");
				}
				String k = (String) ((DomainConceptEQ<?, ?>) (k1List.get(0)))
						.getValue();

				List<Concept> k2List = structuralFindK(ca2, this.arg);
				if (!k2List.isEmpty()) {
					if (k2List.size() > 1) {
						throw new OrderingDescriptionException(
								"Concept not sufficiently descriptive");
					}
					String k2 = (String) ((DomainConceptEQ<?, ?>) (k2List
							.get(0))).getValue();

					setStructuralSubsumptions(getStructuralSubsumptions() + 1);
					int leveltop = k.compareTo(k2);
					if (leveltop == 0) {
						// further comparison required! a:f=22 vs. b:f=22
						leveltop = this.residualOd.compare(ca, ca2);
					}
					return leveltop;
				}
			}
		}
		Concept lhs = new AndConcept(ca.getConcept(), ca2.getConcept()
				.getPrimeCopy());
		Concept rhs = new DomainConceptLT<Role, Role>(Role.createRoleAsIs(arg),
				Role.createRoleAsIs(this.arg + Concept.PRIME));
		KBReason reason = new KBReason(false);
		if (reason.subsumes(lhs, rhs, null)) {
			return -1;
		}
		rhs = new DomainConceptLT<Role, Role>(Role.createRoleAsIs(this.arg
				+ Concept.PRIME), Role.createRoleAsIs(this.arg));
		if (reason.subsumes(lhs, rhs, null)) {
			return 1;
		}
		rhs = new DomainConceptEQ<Role, Role>(Role.createRoleAsIs(this.arg
				+ Concept.PRIME), Role.createRoleAsIs(this.arg));
		if (reason.subsumes(lhs, rhs, null)) {
			return this.residualOd.compare(ca, ca2);
		}
		return 0;
	}

	private List<Concept> structuralFindK(ConceptAssertion ca, String f) {
		LinkedList<Concept> result = new LinkedList<Concept>();
		LinkedList<Concept> q = new LinkedList<Concept>();
		q.add(ca.getConcept());
		while (!q.isEmpty()) {
			Concept con = q.remove();
			if (con instanceof AndConcept) {
				for (Concept and : ((AndConcept) con).getConceptSet()) {
					q.add(and);
				}
			} else if (con instanceof OrConcept) {
				for (Concept or : ((OrConcept) con).getConceptSet()) {
					q.add(or);
				}
			}

			if (con instanceof DomainConceptEQ) {
				DomainConceptEQ<?, ?> bk = (DomainConceptEQ<?, ?>) con;
				if (bk.getLHS().toString().equals(f)) {
					// Debug
					// Object val = ((DomainConceptEQ) con).getValue();
					// Object feat = ((DomainConceptEQ) con).getFeature();
					// System.err.println("FOUND FEATURE: " + feat + " = " +
					// val);
					// ---
					result.add(con);
				}
			}
		}
		return result;
	}

	private int comparePartition(ConceptAssertion ca, ConceptAssertion ca2)
			throws OrderingDescriptionException, CoreReasoningException {
		Concept notPartition = new NotConcept(this.partition);
		if (OrderingDescription.subsumes(ca.getConcept(), this.partition, null)) {
			if (OrderingDescription.subsumes(ca2.getConcept(), this.partition,
					null)) {
				return this.residualOd.compare(ca, ca2);
			} else if (OrderingDescription.subsumes(ca2.getConcept(),
					notPartition, null)) {
				return -1;
			}
		} else if (OrderingDescription.subsumes(ca.getConcept(), notPartition,
				null)) {
			if (OrderingDescription.subsumes(ca2.getConcept(), this.partition,
					null)) {
				return 1;
			} else if (OrderingDescription.subsumes(ca2.getConcept(),
					notPartition, null)) {
				return this.residualOd2.compare(ca, ca2);
			}
		}

		return 0;
	}

	// public void setTBox(TBox tb) {
	// this.tb = tb;
	// }
	//
	// public void setABox(ABox ab){
	// this.ab = ab;
	// }

	// public ABox getABox(){
	// return this.kb.getAbox();
	// }
	// public TBox getTBox() {
	// return this.kb.getTbox();
	// }
	//
	// public DescriptionLogicsKB getKB(){
	// return this.kb;
	// }
	//
	// public void setKB(DescriptionLogicsKB mykb){
	// this.kb = mykb;
	// }

	private static boolean subsumes(Concept a, Concept b, DescriptionLogicsKB kb) {
		KBReason res = new KBReason(false);
		if (!ENABLE_OPTIMIZATIONS) {
			try {
				return res.subsumes(a, b, kb);
			} catch (CoreReasoningException e) {
				e.printStackTrace();
			}
		}
		if (subsumptionCache.get(a) == b) {
			setCacheHits(getCacheHits() + 1);
			return true;
		}
		try {
			if (res.subsumes(a, b, kb)) {
				subsumptionCache.put(a, b);
				return true;
			}
		} catch (CoreReasoningException e) {
			e.printStackTrace();
		}
		return false;
	}

	@Override
	public String toString() {
		String res = "";
		if (this.partition == null) {
			// not a part od
			if (this.residualOd == null) {
				if (this.arg == null) {
					// not (f un)
					res += "[un]";
				} else {
					res += "[" + arg + "_un]";
				}
			} else {
				// f od
				res += "[" + this.arg + "_" + this.residualOd.toString() + "]";
			}
		} else {
			// C (od1 d2)
			res += "[" + this.partition.toString() + "("
					+ this.residualOd.toString() + "_"
					+ this.residualOd2.toString() + ")]";
		}
		return res;
	}

	public String toSerializedString() {
		return this.od;
	}

	public static void setCacheHits(int cacheHits) {
		OrderingDescription.cacheHits = cacheHits;
	}

	public static int getCacheHits() {
		return cacheHits;
	}

	public static void setStructuralSubsumptions(int structuralSubsumptions) {
		OrderingDescription.structuralSubsumptions = structuralSubsumptions;
	}

	public static int getStructuralSubsumptions() {
		return structuralSubsumptions;
	}

	public void setType(String type) {
		this.type = type;
	}

	public String getType() {
		return type;
	}

	public boolean majorSortOnArg(Concept c) {
		if (c.isTop()) {
			return true;
		}
		if (type.equals(QueryKeywords.ORDERING_FEATURE)) {
			if (c instanceof DomainConcept) {
				// only for concepts (f op k)
				Role f = (Role) ((DomainConcept<?, ?>) c).getRealFeature();
				String s = (String) ((DomainConcept<?, ?>) c).getRealValue();
				if (f != null && s != null & f.getRoleName().equals(arg)) {
					return true;
				}
			}
		} else if (type.equals(QueryKeywords.ORDERING_PARTITION)) {
			// TODO: useful at all?
			return false;
		} else if (type.equals(QueryKeywords.ORDERING_UN)) {
			return false;
		}
		return false;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((od == null) ? 0 : od.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (!(obj instanceof OrderingDescription))
			return false;
		OrderingDescription other = (OrderingDescription) obj;
		if (od == null) {
			if (other.od != null)
				return false;
		} else if (!od.equals(other.od))
			return false;
		return true;
	}

}
